package mx.vlabs.soapdroid;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.TypeVariable;
import java.net.URL;

import javax.xml.namespace.QName;

import org.ksoap2.SoapEnvelope;
import org.ksoap2.SoapFault;
import org.ksoap2.serialization.PropertyInfo;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapPrimitive;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;

import mx.vlabs.soapdroid.annotations.RequestWrapper;
import mx.vlabs.soapdroid.annotations.ResponseWrapper;
import mx.vlabs.soapdroid.annotations.WebMethod;
import mx.vlabs.soapdroid.annotations.WebParam;
import mx.vlabs.soapdroid.annotations.WebResult;
import mx.vlabs.soapdroid.dto.DTO;
import mx.vlabs.soapdroid.exception.WebServiceException;
import mx.vlabs.soapdroid.utils.Utils;

public class ClientProxy implements InvocationHandler {
	
	private Class<?> proxyClass;
	private URL wsdlURL;
	private QName serviceName;
	private QName portName;
	
	public ClientProxy(URL wsdlURL,QName serviceName,QName portName, Class<?> proxyClass) {
		// TODO Auto-generated constructor stub
		this.proxyClass = proxyClass;
		this.wsdlURL = wsdlURL;
		this.portName = portName;
		this.serviceName = serviceName;
	}


	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		// TODO Auto-generated method stub
		
		OperationMetadata omd = readOperationMetadata(method);

		DTO request = (DTO) Class.forName(omd.getRequestClassName())
				.getConstructor(String.class,String.class)
				.newInstance(
						omd.getRequestName().getNamespaceURI(),
						omd.getRequestName().getLocalPart()
				 );
		
		if(request==null)
		throw new WebServiceException("error loading request object "+omd.getRequestClassName());
		
		int i = 0;
		
		for(QName paramName : omd.getParamNames()){
			request.setDTOProperty(paramName.getLocalPart(), args[i]);
			i++;
		}
		
		request.explode();
	
		SoapSerializationEnvelope envelope = 
				new SoapSerializationEnvelope(SoapEnvelope.VER10); 
		
		envelope.setOutputSoapObject(request);
		
		try{
			HttpTransportSE androidHttpTransport = new HttpTransportSE(wsdlURL.toString());
			androidHttpTransport.call(omd.getActionName(), envelope);
		
		}catch(Exception e){
			throw new WebServiceException(e);
		}
		
		Object result = envelope.bodyIn;
		
		Object _return = null;
		
		if(result instanceof SoapFault){
			throw (SoapFault) result;
		}else if(result instanceof SoapObject){
			
			SoapObject so = (SoapObject) result;
			
			if(so.getProperty(0) instanceof SoapObject){
				_return = DTO.load((SoapObject) so.getProperty(0),(Class<DTO>) method.getReturnType());
			}else if(so.getProperty(0) instanceof SoapPrimitive){
				_return =  Utils.readPrimitiveValue(method.getReturnType(),((SoapPrimitive) so.getProperty(0)).getValue());
			}
			
		}

		return method.getReturnType().cast(_return);
	}
	


	public OperationMetadata readOperationMetadata(Method method){
		
		OperationMetadata omd = new OperationMetadata();
		String methodName = method.getName();
		WebMethod webMethod = method.getAnnotation(WebMethod.class);
		
		if(webMethod!=null){
			
			if(!webMethod.action().isEmpty())
				omd.setActionName(webMethod.action());
			
			if(!webMethod.operationName().isEmpty())
				omd.setOperationName(webMethod.operationName());
			else
				omd.setOperationName(methodName);
			
			omd.setExclude(webMethod.exclude());
		}else{
			omd.setOperationName(methodName);
		}
		
		RequestWrapper requestWrapper = method.getAnnotation(RequestWrapper.class);
		
		if(requestWrapper!=null){
			String namespace = "";
			String localName = "";
			
			if(!requestWrapper.targetNamespace().isEmpty()){
				namespace = requestWrapper.targetNamespace();
			}
			
			if(!requestWrapper.localName().isEmpty()){
				localName = requestWrapper.localName();
			}
			
			omd.setRequestName(new QName(namespace,localName));
			omd.setRequestClassName(requestWrapper.className());
		}
		
		ResponseWrapper responseWrapper = method.getAnnotation(ResponseWrapper.class);
		
		if(responseWrapper!=null){
			String namespace = "";
			String localName = "";
			
			if(!responseWrapper.targetNamespace().isEmpty()){
				namespace = responseWrapper.targetNamespace();
			}
			
			if(!responseWrapper.localName().isEmpty()){
				localName = responseWrapper.localName();
			}
			
			omd.setResponseName(new QName(namespace,localName));
			omd.setResponseClassName(responseWrapper.className());
		}
		
		WebResult webResult = method.getAnnotation(WebResult.class);
		
		if(webResult!=null){
			String namespace = "";
			String localName = "";
			
			if(!webResult.targetNamespace().isEmpty()){
				namespace = responseWrapper.targetNamespace();
			}
			
			if(!webResult.name().isEmpty()){
				localName = responseWrapper.localName();
			}
			
			omd.setResultName(new QName(namespace, localName));
		}else{
			omd.setResultName(new QName("","return"));
		}
		
		boolean paramAnnotationFound = false;
		
		for(Annotation[] array : method.getParameterAnnotations()){
			for(Annotation annotation : array){
				if(annotation.annotationType().equals(WebParam.class)){
					
					WebParam webParam = (WebParam) annotation;
					
					String namespace = "";
					String localName = "";
					
					if(!webParam.targetNamespace().isEmpty()){
						namespace = webParam.targetNamespace();
					}
					
					if(!webParam.name().isEmpty()){
						localName = webParam.name();
						omd.getParamNames().add(new QName(namespace,localName));
						paramAnnotationFound = true;
					}
					
					
					
				}
			}
		}
		
		if(!paramAnnotationFound){
			for(TypeVariable<Method> tv : method.getTypeParameters()){
				String namespace = "";
				omd.getParamNames().add(new QName(namespace,tv.getName()));
			}
		}
		
		return omd;
	}

}
